﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;

namespace Pahan.McuFusion.Common
{
    [Serializable]
    public abstract class ProgrammerBase<TDevice, TProgrammerInfo, THardwareAdapter> : IProgrammer
        where TDevice : DeviceInfo
        where TProgrammerInfo : ProgrammerInfo
        where THardwareAdapter : IHardwareAdapter
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:System.Object"/> class.
        /// </summary>
        protected ProgrammerBase(TDevice device, TProgrammerInfo info, THardwareAdapter adapter)
        {
            this.Info = info;
            this.device = device;
            this.adapter = adapter;
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Close();
            adapter.Dispose();
        }

        #endregion

        #region Implementation of IProgrammer

        /// <summary>
        /// Gets the information about the programmer.
        /// </summary>
        public ProgrammerInfo Info
        {
            get;
            private set;
        }

        /// <summary>
        /// Opens the programmer. This method must be called before the actual use of the programmer.
        /// </summary>
        public void Open()
        {
            lock (SyncRoot)
            {
                if (worker != null) return;
                
                worker = new BackgroundWorker();
                worker.WorkerReportsProgress = true;
                worker.WorkerSupportsCancellation = true;
                worker.ProgressChanged += worker_ProgressChanged;
                worker.RunWorkerCompleted += worker_RunWorkerCompleted;
                worker.DoWork += worker_DoWork;

                adapter.Open();
                DoOpen();
            }
        }

        /// <summary>
        /// Closes the programmer.
        /// </summary>
        public void Close()
        {
            lock (SyncRoot)
            {
                if (worker != null)
                {
                    if (worker.IsBusy)
                    {
                        throw new InvalidOperationException("Cannot close the programmer while performing operation.");
                    }
                    worker.Dispose();
                    worker = null;
                }
                adapter.Close();
            }
        }

        /// <summary>
        /// Cancels the current asynchronous operation.
        /// </summary>
        /// <seealso cref="IProgrammer.EraseMemoryAsync"/>
        /// <seealso cref="IProgrammer.ReadMemoryAsync"/>
        /// <seealso cref="IProgrammer.WriteMemoryAsync"/>
        public void CancelAsync()
        {
            lock (SyncRoot)
            {
                if (worker != null)
                {
                    worker.CancelAsync();
                }
            }
        }

        /// <summary>
        /// Erases the memory. This method does not block the calling thread.
        /// </summary>
        /// <param name="memory">The memory to erase.</param>
        /// <seealso cref="IProgrammer.AsyncCompleted"/>
        public void EraseMemoryAsync(MemoryClassInfo memory)
        {
            lock (SyncRoot)
            {
                CheckState();
                Action<AsyncContext> action = (context => DoEraseMemory(new MemoryArgument(memory), context));
                worker.RunWorkerAsync(action);
            }
        }

        /// <summary>
        /// Reads the program memory. This method does not block the calling thread.
        /// </summary>
        /// <param name="output">Output stream.</param>
        /// <param name="memory">The memory to read.</param>
        /// <seealso cref="IProgrammer.AsyncCompleted"/>
        public void ReadMemoryAsync(Stream output, MemoryClassInfo memory)
        {
            lock (SyncRoot)
            {
                CheckState();
                Action<AsyncContext> action = (context => DoReadMemory(new MemoryArgument(output, memory), context));
                worker.RunWorkerAsync(action); 
            }
        }

        /// <summary>
        /// Writes the program memory. This method does not block the calling thread.
        /// </summary>
        /// <param name="input">Input stream.</param>
        /// <param name="memory">The memory to write.</param>
        /// <seealso cref="IProgrammer.AsyncCompleted"/>
        public void WriteMemoryAsync(Stream input, MemoryClassInfo memory)
        {
            lock (SyncRoot)
            {
                CheckState();
                Action<AsyncContext> action = (context => DoWriteMemory(new MemoryArgument(input, memory), context));
                worker.RunWorkerAsync(action); 
            }
        }

        public event AsyncCompletedEventHandler AsyncCompleted;
        public event ProgressChangedEventHandler ProgressChanged;

        #endregion

        #region Protected Members

        /// <summary>
        /// Performs an actual action to open the programmer.
        /// </summary>
        protected abstract void DoOpen();

        /// <summary>
        /// Read the device memory.
        /// </summary>
        /// <param name="arg">
        /// Contains the instance of <see cref="MemoryClassInfo"/> and
        /// the output stream for the memory content.
        /// </param>
        /// <param name="context">Asynchronous operation context.</param>
        protected abstract void DoReadMemory(MemoryArgument arg, AsyncContext context);
        
        /// <summary>
        /// Writes the device memory.
        /// </summary>
        /// <param name="arg">
        /// Contains the instance of <see cref="MemoryClassInfo"/> and
        /// the intput stream with the memory content.
        /// </param>
        /// <param name="context">Asynchronous operation context.</param>
        protected abstract void DoWriteMemory(MemoryArgument arg, AsyncContext context);

        /// <summary>
        /// Erases the device memory.
        /// </summary>
        /// <param name="arg">
        /// Contains the instance of <see cref="MemoryClassInfo"/>.
        /// </param>
        /// <param name="context">Asynchronous operation context.</param>
        protected abstract void DoEraseMemory(MemoryArgument arg, AsyncContext context);

        /// <summary>
        /// Gets the adapter.
        /// </summary>
        protected THardwareAdapter Adapter
        {
            get
            {
                return adapter;
            }
        }

        /// <summary>
        /// Get the device.
        /// </summary>
        protected TDevice Device
        {
            get
            {
                return device;
            }
        }

        /// <summary>
        /// The object that can be used to synchronize access to the programmer.
        /// </summary>
        protected readonly Object SyncRoot = new Object();

        #endregion

        #region Private Members

        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            var action = (Action<AsyncContext>)e.Argument;
            var context = new AsyncContext((BackgroundWorker)sender);
            action.Invoke(context);
            e.Cancel = context.Cancel;
        }

        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            var handler = AsyncCompleted;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void CheckState()
        {
            if (worker == null)
            {
                throw new InvalidOperationException("The programmer must be opened before executing this operation.");
            }
            if (worker.IsBusy)
            {
                throw new InvalidOperationException("The programmer is performing another operation at the moment");
            }
        }

        private readonly TDevice device;

        private readonly THardwareAdapter adapter;

        [NonSerialized]
        private BackgroundWorker worker;

        #endregion

        #region Nested Classes

        protected class MemoryArgument
        {
            public MemoryArgument(MemoryClassInfo memory)
                : this(null, memory)
            {
            }

            public MemoryArgument(Stream stream, MemoryClassInfo memory)
            {
                this.Stream = stream;
                this.Memory = memory;
            }

            public Stream Stream
            {
                get;
                private set;
            }

            public MemoryClassInfo Memory
            {
                get;
                private set;
            }
        }

        protected class AsyncContext
        {
            internal AsyncContext(BackgroundWorker worker)
            {
                this.worker = worker;
            }

            public bool CancellationPending
            {
                get
                {
                    return worker.CancellationPending;
                }
            }

            public void ReportProgress(int progress)
            {
                worker.ReportProgress(progress);
            }

            public bool Cancel
            {
                get;
                set;
            }

            private readonly BackgroundWorker worker;
        }

        #endregion
    }
}
